/*
Copyright (c) 2007 salesforce.com, inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
global class Friends {
	// The WebService keyword makes this a public
	// WebService method. WebService methods must
	// always be static, and must also be contained
	// in a global class.
	static Friend__c viewer = new Friend__c(); 
	WebService static String isViewer(String arg) {	
		Friend__c me = getMe();  
		system.assert(me!=null);
		String myid =  me.User__c;
		system.debug(myid+' '+arg);
		return ( myid == arg ? 'true':'false'); 
	}
	WebService static Friend__c getMe() { 
		// me is always my user record transformed into a Friend objet
		if( viewer.Id == null) { 
			viewer = userToFriend( 
				[select id,lastname,firstname,Name,email,photo__c from User where id = :UserInfo.getUserId() limit 1 ]
			);
			System.debug(viewer);
		}
		return viewer;
	}	
	static testMethod void testGetMe() {
		Friend__c f = getMe(); 
		system.assert(f!=null);
		System.assertEquals('true', isViewer( UserInfo.getUserId() ));
		System.assertNotEquals( f.Display_Name__c ,null, 'missing display name');
		System.assertNotEquals( f.User__c,null, 'missing user id' );
		System.assertNotEquals( f.Email__c ,null, 'missing email');
		
	}	
	
	/* Reference for Opportunity object friendship, 
	 *  use as is or define your own concept of "friends for the deal"
	 *  can be extend to other standard or custom objects
	 */
	WebService static List<Friend__c> getContacts(ID id) { 
		Opportunity opp = [select id,name,ownerid from opportunity where id=:id limit 1];	
		System.Assert(opp!=null);

		List<ID> clist = new List<ID>();
		
		// get a list of contacts that have touched or are touched 
		// by this object, currently we will assume it is an opportunity 
		// that can be extended in the future
		OpportunityContactRole[] ocr = [ Select Id, ContactId From OpportunityContactRole where 
			OpportunityId = :id];
				
		// gather ids into a list of contact id's to support a retrieve
		for (OpportunityContactRole o:ocr) { 
			clist.add(o.ContactId); 	
		}
		
		// TODO add more touch points here, activities, salesteam? etc.		
		for(Task t :[select whoid ,id, whatid from task where whatid = :id and whoid!=null]){
			clist.add( t.whoid );
		}	
		//	system.debug(clist);
		
		// list we will be sending back
		List<Friend__c> ret = new List<Friend__c>();
		
		/*
		 * add more users below here, like sales teams 
		 */		
		List<ID> ulist = new List<ID>();	 
		ulist.add(opp.OwnerId);
		System.assert(ulist.size() ==1);
		
		for(User u : [ select id,firstname,lastname,email,Photo__c,name, phone
								from User where Id IN :ulist limit 100]) { 
			ret.add( userToFriend(u) ); 
		}
	
		// now that we have a list of contact ids get the details and fill in a friends list
		Map<ID, Contact> contactList = new Map<ID, Contact>([ 
			select id,firstname,lastname,email,Photo__c,name, phone
			from contact where Id IN :clist.clone() limit 100]);
		// system.debug(clist); 

		// now build the ret list, listing contacts more than once if they exist in clist multiple times
		for( ID cid:clist) { 		
			Contact c=contactList.get(cid);
			system.assert(c!=null);
			ret.add( contactToFriend( contactList.get(cid) ) );	
		}
		return ret;
	}
	
	/* helper methods */
	private static Friend__c contactToFriend(Contact c) { 
		// would like to set the Id but it's readonly
		Friend__c r = new Friend__c(Contact__c = c.Id, Display_Name__c = 
			c.Name , email__c =c.email ,
			Photo__c = c.Photo__c );
		/* Select f.User__c, f.SystemModstamp, f.Photo__c, f.OwnerId, f.Opportunity__c, f.Name, 
		f.LastModifiedDate, f.LastModifiedById, f.IsDeleted, f.Id, f.Email__c, f.Display_Name__c,
		 f.CreatedDate, f.CreatedById, f.Contact__c From Friend__c f 
		 */
		return r;
	}
	private static Friend__c userToFriend(User u) { 
		Friend__c r = new Friend__c(Display_Name__c = 
			u.Name,	email__c = u.email ,Photo__c = u.Photo__c,
			User__c = u.Id);
		/* 
		 * NOTE custom fields Photo__c that this class depends on
		 * 
		u.Username, u.UserType, u.UserRoleId, u.UserPreferencesTaskRemindersCheckboxDefault, 
		u.UserPreferencesReminderSoundOff, u.UserPreferencesEventRemindersCheckboxDefault, 
		u.UserPreferencesActivityRemindersPopup, u.UserPermissionsOfflineUser, u.UserPermissionsMobileUser, 
		u.UserPermissionsMarketingUser, u.UserPermissionsCallCenterAutoLogin, u.Title, 
		u.TimeZoneSidKey, u.SystemModstamp, u.Street, u.State, u.ReceivesInfoEmails, 
		u.ReceivesAdminInfoEmails, u.ProfileId, u.PostalCode, u.Photo__c, u.Phone, 
		u.OfflineTrialExpirationDate, u.OfflinePdaTrialExpirationDate, u.Name, 
		u.MobilePhone, u.LocaleSidKey, u.LastName, u.LastModifiedDate, u.LastModifiedById, 
		u.LastLoginDate, u.LanguageLocaleKey, u.IsActive, u.Id, u.ForecastEnabled, u.FirstName, 
		u.Fax, u.Extension, u.EmployeeNumber, u.EmailEncodingKey, u.Email, u.Division, 
		u.Department, u.DelegatedApproverId, u.CreatedDate, u.CreatedById, 
		u.Country, u.CompanyName,
		 u.City, u.CallCenterId, u.Alias From User u
		*/
		return r;
	}
	static testMethod void testGetContacts() { 
		List<Friend__c> l = getContacts('006T0000001gWdU');
		system.assert(l.size()>0);
		system.debug('get contacts '+l);
		Opportunity o = new Opportunity(name='foo',closedate=system.today(),stagename='open');
		insert o;
		system.assert(o.id!=null);
		Contact cc = new Contact(lastname='me');
		insert cc;
		Task t = new Task(whatid=o.id,whoid=cc.id, subject ='bar');
		insert t;
		Task t2 = new Task(whatid=o.id,whoid=cc.id, subject ='lskdjsldkfj');
		insert t2;

		List<Friend__c> l2 = getContacts(o.id);
		for(Friend__c fff:l2) { system.debug(fff); }
		system.assert(l2.size() ==3);
	}

	/* Activities 
	 * Task object must have a custom field Url__c
	 */
	// TODO add a what id?
	WebService static ID postActivity( String subject, String datein, String description,
		String url ) {
		Task t = new Task();
		t.Subject = subject;
		t.ActivityDate =  Date.valueOf(datein);
		t.Description = description;
		t.Url__c = url;
		insert t; return t.Id;
	}	
	WebService static List<Task> getActivities() { 
		return getActivitiesFor(UserInfo.getUserId());
	} 
	WebService static List<Task> getActivitiesFor(ID own) {
		// very non selective.. should have sliding time window
		return [ Select  t.WhoId, t.Subject, t.Status, t.Url__c, t.Priority, t.Id, 
					t.ActivityDate, t.isClosed , t.CreatedDate , t.Description 
					From Task t where ownerid = :own
					order by ActivityDate desc limit 100
				];
	}
	static testMethod void testActivities() { 
		List<Task> l = getActivities(); 
		system.assert( l.size() > 0); 	
		l = getActivitiesFor( [select id from User where Id != :UserInfo.getUserId() ].Id); 
		system.assert( l.size() > 0); 	
		ID aid = postActivity('test activity','2007-10-22','Description','http://www.salesforce.com');
		system.debug(aid);
		system.assert(aid!=null);
	}
	
	/*  Friends__c is a custom object that we use as a junction to represent one 
	 	friend to friend relationship.
	 
		extend this to meet your definition of friends, currently the Friend object has 
  		fields for opportunity, contact and user
  		so that you can have "friends of the deal", and friends of the user, friends may 
  		be contacts or users
  		
  		note custom fields for Friend__c object: 
  		f.User__c,  f.Photo__c,  f.Opportunity__c, 
  		f.Name,  f.Id, f.Email__c, f.Display_Name__c,  f.Contact__c From Friend__c f
 	  */
	WebService static List<Friend__c> getFriends() { 
		// todo change to a Friends list
		List <Friend__c> fl = [select id,
		f.Contact__r.Photo__c, f.Contact__r.Salutation, f.Contact__r.Name,
				f.Contact__r.FirstName, f.Contact__r.LastName,
				f.Contact__c, f.Contact__r.Email,
				f.User__r.Photo__c, f.User__r.Name, f.User__r.Email  		
				From Friend__c f 
			where f.ownerid = :UserInfo.getUserId() 
			    or f.User__c = :UserInfo.getUserId()  
			limit 6];
		List <Friend__c> ret = new List<Friend__c>();
		for( Friend__c f:fl) {
			if(f.Contact__r!=null) { ret.add( contactToFriend( f.Contact__r) ); }
			if(f.User__r!=null) { ret.add( userToFriend( f.User__r) ); }
			
		}	
		// returning duplicates is supported, they can be counted by the app
		return ret;
	}
	
	/* 
	people.requestMakePersonAFriend(person, opt_onSuccess, opt_onFailure)
	Request for the container to make the specified person a friend.
	Person, String Person or ID of person to make a friend
	
	people.requestMakePersonNotAFriend(person, opt_onSuccess, opt_onFailure)
	Request for the container to make the specified person not a friend.
	
	TODO not tested
	NB: this assumes ownerid == person to add friend to
	Simply create a Friend__c object for the provided id and current users
	*/
	WebService static String makePersonAFriend(String email, ID user_or_obj) { 
		// find this person (user or contact) , relate it to the user or object
		Friend__c f = new Friend__c( ) ; 
		// just upsert this friend
		if ( Pattern.matches('^005.*',user_or_obj) ) {
			f.User__c = user_or_obj;
		} 
		else if ( Pattern.matches('^006.*',user_or_obj) ) {
			f.Opportunity__c = user_or_obj; 
		}
		upsert f;
		return 'true';	
	}
	static testMethod void testFriends2() {
		string s = makePersonAFriend('fo@bar.com',UserInfo.getUserId());
		system.assert(s!=null);
		Opportunity o = [select id from Opportunity limit 1];
		s = makePersonAFriend('fo@bar.com',o.Id);
		system.assert(s!=null);
	}
	/* 
	people.Person.hasApp(The)
	Check for whether a person has data for the given application. This is different from whether the application is on the page.
	TODO not tested
 	*/
	WebService static String hasApp(String appname  ,String personid) { 
		Integer i = [ select count() from AppField__c where app_name__c = :appname
		 and ownerId = :UserInfo.getUserId() limit 1];
		return ( i<=0?'false':'true');
	}
	WebService static String getAppFieldUpdateRequest(String appname, String key) {
		// return the value in the app field key for the current user
		AppField__c af = null; 
		 try { 
			af = [ select a.value__c from appfield__c a 
			where ownerid = :UserInfo.getUserId() and 
			app_name__c = :appname and
			app_field__c = :key		limit 1	];
		 } catch (Exception e) { 
			return null; 
		 }
		return af.value__c;
	}
	//  post a field update
	WebService static void setAppFieldUpdateRequest( String appname, 
													String key, 
													String valu){
		AppField__c af = null;
		try { 
			af = [ select a.id, a.value__c from appfield__c a 
			where ownerid = :UserInfo.getUserId() and 
			app_name__c = :appname and
			app_field__c = :key		limit 1	];
		} catch(Exception e) { } 
		if ( af != null ) { 
			af.value__c = valu;
			update af;	
		}
		else { 
			af = new AppField__c(App_Name__c = appname,
				App_Field__c = key, Value__c = valu);
			insert af;
		}
	}
	
	// called at init or getMe to have all the props on hand in JS
	WebService static List<AppField__c> getAppFields(string appname){
		return [Select a.Value__c, a.OwnerId, 
		a.Name, a.App_Name__c, a.App_Field__c 
		 From AppField__c a where a.name = :appname 
		 order by a.app_name__c
		];
	}
	// The following is a simple unit test for the
	// getFriends method. Unit test methods take
	// no arguments, commit no data to the database,
	// and are flagged with the testMethod keyword
	// in the definition.

	static testMethod void testFriends() {
		System.assertEquals('true', isViewer( UserInfo.getUserId() ));
		User you = [ select id from user where id != :UserInfo.getUserId() limit 1];
		system.assertNotEquals(you,null);
		System.assertEquals('false', isViewer( you.Id ));
		
		system.assert( getAppFields('').size() > 0 );
		system.debug('found friends :' + getFriends() );
		system.debug('\n\n me '+ getMe());
		
		system.debug('\n\n me '+ getAppFieldUpdateRequest('myapp bogus','foo'));
		setAppFieldUpdateRequest('appname','key','value');
		String s = getAppFieldUpdateRequest('appname','key');
		system.assertEquals(s,'value');

		String has = hasApp('none',UserInfo.getUserId() );
		System.assertEquals(has,'false');		
		has = hasApp('appname',UserInfo.getUserId() );
		System.assertEquals(has,'true');
		
		setAppFieldUpdateRequest('appname','key','value2');
		s = getAppFieldUpdateRequest('appname','key');
		system.assertEquals(s,'value2');
	}
}